接下來我們要來談談,應該不少人常聽到的『 Repository 』這個東東,目前我先將他放在 3-Tier Layer 中的 DataSourceLayer 的部份,但是在書中是放在 :
Object-Relational Metadata Mapping Patterns
接下來我們來研究看看他到底是什麼。
Mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects.
我個人的理解為 :
data mapping 就是單純的資料庫操作,所以他提供的方法名很接近資料庫處理,例如 select、update 等。而 repository 傾向接近 domain 層,所以他的命名基本上是偏向 findByName、save 啥的。
我覺得這裡可以想簡單定義以為 repository 與 data mapper 的差別 :
那這樣我可以理解為什麼要在書中把他定義在『 Object-Relational Metadata Mapping Patterns 』層級了。因為嚴格來說它是為了讓 domain layer 與 data source layer 中間在一個層級。
// Domain Layer
class PersonDomain{
id: string
age: number
name: string
company: string
constructor(id:string, age: number, name: string,company: string){
this.id = id
this.age = age
this.name = name
this.company = company
}
isAdult(): boolean{
return this.age >= 18
}
isVIP(): boolean{
return ['HAHOW','GOOGLE','KKBOX'].includes(this.company)
}
}
// DataSource Layer
function _mockExecuteSelectSql(sql){
return [
{
id: '1',
name: 'mark',
age: 18,
company: 'HAHOW'
}
]
}
interface IPersonRepository{
findById(id: string): PersonDomain
findByCompany(company: string): PersonDomain[]
save(person: PersonDomain)
}
class PersonRepository implements IPersonRepository {
personMapper: IPersonMapper
constructor(personMapper: IPersonMapper){
this.personMapper = personMapper
}
findById(id: string): PersonDomain{
const result = this.personMapper.selectByQuery({
id
})
return result[0]
}
findByCompany(company: string): PersonDomain[]{
const result = this.personMapper.selectByQuery({
company
})
return result
}
save(person: PersonDomain): void{
this.personMapper.insert(person)
}
}
interface IPersonMapper{
selectByQuery(query: any): PersonDomain[]
insert(person: PersonDomain): void
}
class PersonMapper implements IPersonMapper {
selectByQuery(query: any): PersonDomain[]{
console.log('Connect to db for find')
const querystring = query
const resultSet: any = _mockExecuteSelectSql(querystring)
const result = this.doLoad(resultSet)
return result
}
insert(person: PersonDomain): void{
}
private doLoad(resultSet: any): PersonDomain[]{
const result = []
for (const data of resultSet) {
result.push(new PersonDomain(data.id, data.age, data.name, data.company))
}
return result
}
}
// App Layer
const personMapper: IPersonMapper = new PersonMapper()
const personRepository: IPersonRepository = new PersonRepository(personMapper)
const personsInCompany: PersonDomain[] = personRepository.findByCompany('HAHOW')
if(personsInCompany.length >= 10) throw Error('公司人數已達限制')
const newPerson: PersonDomain = new PersonDomain(null, 18, 'Mark', 'HAHOW')
if(!newPerson.isAdult()) throw Error('要成年才能註冊喔')
personRepository.save(newPerson)
之前我一直在想,如果 domain 層有很多需要為了業務,而產生的一些 query,例如 SelectByNameAndAgeAndCompany 這種的,那是不是要在 data mapper 那『 為了業務而寫一個方法支援它呢 ? 』。
那這樣不就 domain model 與 dataMapper 耦合在一起了,理解了 repository 才知道這個層級就是為了解決這個問題。
上一篇文章中我們有問了一個問題如下 :
Repository 就是指 DataMapper 嗎 ?
不是,它比較接近 domain 層,它會將一些 domain 複雜的 query 資料庫操作,在 repository 裡處理,例如一些 n+1 的 i/o 操作,應該也會在這裡進行優化。